#include "libGeoutils/tool/color_mapper.h"
#include "nlohmann/json.hpp"
#include <sstream>

#define NAME_PROP "name"
#define NUMBER_PROP "number"
#define COLOR_SEGMENT_PROP "segment"
#define START_COLOR_PROP "start_color"
#define END_COLOR_PROP "end_color"
#define GRAY_KEY "gray"
#define START_R "start_r"
#define START_G "start_g"
#define START_B "start_b"
#define START_A "start_a"
#define END_R "end_r"
#define END_G "end_g"
#define END_B "end_b"
#define END_A "end_a"
#define R_KEY "R"
#define G_KEY "G"
#define B_KEY "B"
#define A_KEY "A"

geo::tool::ColorMapper::ColorMapper(const std::string& name, int number)
{
    mStringProps[NAME_PROP] = name;
	mIntProps[NUMBER_PROP] = number;
	mStartingColor = std::make_shared<ColorRGBA>();
	mEndingColor = std::make_shared<ColorRGBA>(255, 255, 255);
}

geo::tool::ColorMapper::~ColorMapper()
{
}

void geo::tool::ColorMapper::setName(const std::string& name)
{
	mStringProps[NAME_PROP] = name;
}

std::string geo::tool::ColorMapper::getName() const
{
    if (mStringProps.count(NAME_PROP)) {
        return mStringProps.at(NAME_PROP);
    } return "";
}

void geo::tool::ColorMapper::setNumber(int number)
{
	mIntProps[NUMBER_PROP] = number;
}

int geo::tool::ColorMapper::getNumber() const
{
	if (mIntProps.count(NUMBER_PROP)) {
		return mIntProps.at(NUMBER_PROP);
	} return -1;
}

void geo::tool::ColorMapper::setStartingColor(ColorRGBA::sptr color)
{
	if (color) {
		mStartingColor = color;
	}
}

void geo::tool::ColorMapper::setEndingColor(ColorRGBA::sptr color)
{
	if (color) {
		mEndingColor = color;
	}
}

void geo::tool::ColorMapper::appendColorStop(double gray_value, ColorRGBA::sptr color)
{
    if (color && gray_value > 0 && gray_value < 1) {
        mColors[gray_value].first = color;
        mColors[gray_value].second = color;
    }
}

void geo::tool::ColorMapper::appendColorStop(double gray_value, ColorRGBA::sptr end_color, ColorRGBA::sptr start_color)
{
	if (start_color && end_color && gray_value > 0 && gray_value < 1) {
		mColors[gray_value].first = end_color;
		mColors[gray_value].second = start_color;
	}
}

static geo::tool::ColorRGBA::sptr interpolate_colors(geo::tool::ColorRGBA::sptr c1, geo::tool::ColorRGBA::sptr c2, double factor)
{
    if (c1 && c2) {
        unsigned r = static_cast<unsigned>(c1->getR() + (c2->getR() - c1->getR()) * factor);
        unsigned g = static_cast<unsigned>(c1->getG() + (c2->getG() - c1->getG()) * factor);
        unsigned b = static_cast<unsigned>(c1->getB() + (c2->getB() - c1->getB()) * factor);
        unsigned a = static_cast<unsigned>(c1->getA() + (c2->getA() - c1->getA()) * factor);
        return std::make_shared<geo::tool::ColorRGBA>(r, g, b, a);
    }
    return std::make_shared<geo::tool::ColorRGBA>();
}

geo::tool::ColorRGBA::sptr geo::tool::ColorMapper::mapGray2Color(double gray_value) const
{
	if (gray_value <= 0) {
		return mStartingColor;
	} else if (gray_value >= 1) {
		return mEndingColor;
	} else if (mColors.empty()) {
        return interpolate_colors(mStartingColor, mEndingColor, 0.5);
    }

	double start_gray = 0, end_gray = 1;
	geo::tool::ColorRGBA::sptr start_sptr, end_sptr;

    auto it_upper = mColors.lower_bound(gray_value);
    if (it_upper == mColors.begin()) 
	{
		start_gray = 0;
		start_sptr = mStartingColor;
		end_gray = it_upper->first;
		end_sptr = it_upper->second.first;
    }
	else if (it_upper == mColors.end()) 
	{
		start_gray = mColors.rbegin()->first;
		start_sptr = mColors.rbegin()->second.second;
		end_gray = 1;
		end_sptr = mEndingColor;
	} 
	else 
	{
		start_gray = std::prev(it_upper)->first;
		start_sptr = std::prev(it_upper)->second.second;
		end_gray = it_upper->first;
		end_sptr = it_upper->second.first;
	}

    double factor = (gray_value - start_gray) / (end_gray - start_gray);
    return interpolate_colors(start_sptr, end_sptr, factor);
}

void geo::tool::ColorMapper::clearStops()
{
    mColors.clear();
}

bool geo::tool::ColorMapper::to_string(std::string& out_str) const
{
    std::stringstream ss;
    Properties::to_string(out_str);
    ss << out_str;

    ss << "color mapper: [\n";
	ss << "gray[0]: " << mStartingColor->operator std::string() << " - gray[";
    for (const auto& loopColor : mColors) {
		ss << loopColor.first << "]: "<< loopColor.second.first->operator std::string();
		ss << "\ngray[" << loopColor.first << "]: " << loopColor.second.second->operator std::string();
		ss << " - gray[";
    }
	ss << "1]: " << mEndingColor->operator std::string() << "\n";
    ss << "]";
    out_str = ss.str();
    return true;
}

bool geo::tool::ColorMapper::to_json(std::string& out_str) const
{
    nlohmann::json json;
	nlohmann::json json_colors;
	nlohmann::json json_start;
	nlohmann::json json_end;

	try
	{
		json_start[R_KEY] = mStartingColor->getR();
		json_start[G_KEY] = mStartingColor->getG();
		json_start[B_KEY] = mStartingColor->getB();
		json_start[A_KEY] = mStartingColor->getA();

		json_end[R_KEY] = mEndingColor->getR();
		json_end[G_KEY] = mEndingColor->getG();
		json_end[B_KEY] = mEndingColor->getB();
		json_end[A_KEY] = mEndingColor->getA();

		for (const auto& gray_color_pair : mColors) {
			ColorRGBA::sptr end_color = gray_color_pair.second.first;
			ColorRGBA::sptr start_color = gray_color_pair.second.second;
			if (end_color && start_color) {
				nlohmann::json color_json;
				color_json[END_R] = end_color->getR();
				color_json[END_G] = end_color->getG();
				color_json[END_B] = end_color->getB();
				color_json[END_A] = end_color->getA();
				color_json[START_R] = start_color->getR();
				color_json[START_G] = start_color->getG();
				color_json[START_B] = start_color->getB();
				color_json[START_A] = start_color->getA();
				color_json[GRAY_KEY] = gray_color_pair.first;
				json_colors.push_back(color_json);
			}
		}
		json[NAME_PROP] = getName();
		json[NUMBER_PROP] = getNumber();
		json[START_COLOR_PROP] = json_start;
		json[COLOR_SEGMENT_PROP] = json_colors;
		json[END_COLOR_PROP] = json_end;

		out_str = json.dump();
		return true;
	}
	catch (const std::exception& e)
	{
        mLastErrorMessage = "colormapper to_json exception!";
		return false;
	}
}

bool geo::tool::ColorMapper::from_json(const std::string& in_str)
{
	nlohmann::json json;
	try
	{
		json = nlohmann::json::parse(in_str);
		if (json.contains(START_COLOR_PROP)) {
			nlohmann::json json_start = json[START_COLOR_PROP];
			unsigned r = json_start.value(R_KEY, 0);
			unsigned g = json_start.value(G_KEY, 0);
			unsigned b = json_start.value(B_KEY, 0);
			unsigned a = json_start.value(A_KEY, 255);
			mStartingColor = std::make_shared<ColorRGBA>(r, g, b, a);
		}
		if (json.contains(END_COLOR_PROP)) {
			nlohmann::json json_end = json[END_COLOR_PROP];
			unsigned r = json_end.value(R_KEY, 255);
			unsigned g = json_end.value(G_KEY, 255);
			unsigned b = json_end.value(B_KEY, 255);
			unsigned a = json_end.value(A_KEY, 255);
			mEndingColor = std::make_shared<ColorRGBA>(r, g, b, a);
		}
		if (json.contains(COLOR_SEGMENT_PROP)) {
			nlohmann::json json_colors = json[COLOR_SEGMENT_PROP];
			for (const auto& color_json : json_colors) {
				double gray_value = color_json.value(GRAY_KEY, 0.0);
				unsigned end_r = color_json.value(END_R, 0);
				unsigned end_g = color_json.value(END_G, 0);
				unsigned end_b = color_json.value(END_B, 0);
				unsigned end_a = color_json.value(END_A, 255);
				unsigned start_r = color_json.value(START_R, 0);
				unsigned start_g = color_json.value(START_G, 0);
				unsigned start_b = color_json.value(START_B, 0);
				unsigned start_a = color_json.value(START_A, 255);
				ColorRGBA::sptr end_color = std::make_shared<ColorRGBA>(end_r, end_g, end_b, end_a);
				ColorRGBA::sptr start_color = std::make_shared<ColorRGBA>(start_r, start_g, start_b, start_a);
				appendColorStop(gray_value, end_color, start_color);
			}
		}
		if (json.contains(NAME_PROP)) {
			setName(json[NAME_PROP].get<std::string>());
		}
		if (json.contains(NUMBER_PROP)) {
			setNumber(json[NUMBER_PROP].get<int>());
		}
		return true;
	}
	catch (const std::exception& e)
	{
		mLastErrorMessage = "colormapper from_json exception!";
		return false;
	}
}
